#include "PublicHeader.h"
#include "ErrorOutput.h"
#include "OpenUmdfInterfaceTest.h"

TCHAR PIPE_NAME[] = TEXT("\\\\.\\pipe\\ZglxTestPipe");

HANDLE OpenDeviceInterface(DWORD dwDesiredAccess);

void startServer();

HANDLE OpenDeviceInterfaceRW() {
    return OpenDeviceInterface(GENERIC_READ | GENERIC_WRITE);
}

HANDLE OpenDeviceInterfaceRO() {
    return OpenDeviceInterface(GENERIC_READ);
}

void openDeviceFaile() {

}

bool AdapterEchoTest() {
    bool result = false;

    WCHAR pwInputBuffer[] = L"Hello from User Mode App!\n";

    WCHAR pwOutputBuffer[256];
    DWORD bytesReturned = 0;

    HANDLE hDeviceInterface;

    hDeviceInterface = OpenDeviceInterfaceRW();
    if (hDeviceInterface == INVALID_HANDLE_VALUE)
        return false;

    if (!DeviceIoControl(
        hDeviceInterface,
        IOCTL_ADAPTER_ECHO,
        pwInputBuffer, sizeof(pwInputBuffer),
        pwOutputBuffer, sizeof(pwOutputBuffer),
        &bytesReturned, NULL)) {
        PrintCSBackupAPIErrorMessage(GetLastError());
        result = false;
        goto CleanUp0;
    }
	std::_tcout << "Message returned from driver: " << std::endl;
    result = true;

CleanUp0:
    CloseHandle(hDeviceInterface);

    return result;
}

bool NewMonitorTest()
{
    bool result = false;
    HANDLE hDeviceInterface;

    MONITOR_ARRIVE_ARG_OUT MonitorArriveArgOut;
    DWORD bytesReturned = 0;

    hDeviceInterface = OpenDeviceInterfaceRO();
    if (hDeviceInterface == INVALID_HANDLE_VALUE)
        return false;

    if (!DeviceIoControl(
        hDeviceInterface,
        IOCTL_MONITOR_ARRIVE,
        NULL, 0,
        &MonitorArriveArgOut, sizeof(MONITOR_ARRIVE_ARG_OUT),
        &bytesReturned, NULL)) {
        PrintCSBackupAPIErrorMessage(GetLastError());
        result = false;
        goto CleanUp0;
    }

    result = true;

    std::_tcout << "New Monitor Index: " << MonitorArriveArgOut.dwMonitorIndex << std::endl;

CleanUp0:
    CloseHandle(hDeviceInterface);

    return result;
}

bool DelMonitorTest()
{
	bool result = false;
	HANDLE hDeviceInterface;

	MONITOR_ARRIVE_ARG_OUT MonitorArriveArgOut;
	DWORD bytesReturned = 0;

	hDeviceInterface = OpenDeviceInterfaceRO();
	if (hDeviceInterface == INVALID_HANDLE_VALUE)
		return false;

	if (!DeviceIoControl(
		hDeviceInterface,
		IOCTL_MONITOR_DEPART,
		NULL, 0,
		0, 0,
		&bytesReturned, NULL)) {
		PrintCSBackupAPIErrorMessage(GetLastError());
		result = false;
		goto CleanUp0;
	}

	result = true;

CleanUp0:
	CloseHandle(hDeviceInterface);
	return result;
}

const char* GetVersionTest()
{
	HANDLE hDeviceInterface = OpenDeviceInterfaceRW();
	if (hDeviceInterface == INVALID_HANDLE_VALUE)
		return NULL;

	bool result = false;
	HDEVINFO hardwareDevInfo = SetupDiGetClassDevs((LPGUID)&GUID_DEV_CLASS_DISPLAY_ADAPTER, 
		NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);	

	SP_DEVINFO_DATA devInfo;
	devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
	std::string sver;
	if (SetupDiEnumDeviceInfo(hardwareDevInfo, 0, &devInfo)) {
		if (SetupDiBuildDriverInfoList(hardwareDevInfo, &devInfo, SPDIT_COMPATDRIVER)) {
			SP_DRVINFO_DATA drvInfo;
			drvInfo.cbSize = sizeof(SP_DRVINFO_DATA);
			int i = 0;
			while (1) {
				try {
					if (SetupDiEnumDriverInfo(hardwareDevInfo, &devInfo, SPDIT_COMPATDRIVER, i++, &drvInfo)) {
						ULARGE_INTEGER version;
						version.QuadPart = drvInfo.DriverVersion;
						if (!wcsncmp(drvInfo.MfgName, L"BijieNetWorks",13))
						{	
							sver = std::to_string(HIWORD(version.HighPart)) + "." + std::to_string(LOWORD(version.HighPart))
									+ "." + std::to_string(HIWORD(version.LowPart)) + "." + std::to_string(LOWORD(version.LowPart));
							printf("get bj version: %s \n", sver.c_str());
						}
						std::_tcout << "mfgname: " << drvInfo.MfgName
							<< " providername:  " << drvInfo.ProviderName
							<< " Description: " << drvInfo.Description << std::endl;
					}
					else
						break;
				}
				catch (std::exception ex) {
					break;
				}
			}
		}
	}
	SetupDiDestroyDeviceInfoList(hardwareDevInfo);
	return sver.c_str();
}

HANDLE OpenDeviceInterface(DWORD dwDesiredAccess) {
    HDEVINFO							hardwareDevInfo;
    SP_DEVICE_INTERFACE_DATA			deviceInterfaceData;

    PSP_DEVICE_INTERFACE_DETAIL_DATA	pDeviceInterfaceDetailData = NULL;
    ULONG								predictedLen = 0;
    ULONG								requiredLen = 0;

    HANDLE								hDeviceInterface = INVALID_HANDLE_VALUE;

    hardwareDevInfo = SetupDiGetClassDevs(
        (LPGUID)&GUID_DEV_CLASS_DISPLAY_ADAPTER,
        NULL,
        NULL,
        (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); // Only Devices present & Function class devices

    if (hardwareDevInfo == INVALID_HANDLE_VALUE) {
		INFO("SetupDiGetClassDevs failed!\n");
        PrintCSBackupAPIErrorMessage(GetLastError());
        return hDeviceInterface;
    }

    deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

    if (!SetupDiEnumDeviceInterfaces(hardwareDevInfo,
        0, // Can be NULL since the device is uniquely identified by GUID
        (LPGUID)&GUID_DEV_CLASS_DISPLAY_ADAPTER,
        0,
        &deviceInterfaceData)) {
		INFO("SetupDiEnumDeviceInterfaces failed!\n");
        PrintCSBackupAPIErrorMessage(GetLastError());
        goto CleanUp0;
    }

    INFO("Enumerate Interface Data Succeed!\n");

    // Get the required buffer size for our SP_DEVICE_INTERFACE_DATA in which stores the detailed
    // info about the Indirect Display interface
    SetupDiGetDeviceInterfaceDetail(
        hardwareDevInfo,
        &deviceInterfaceData,
        NULL,
        0,
        &requiredLen,
        NULL);
    if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
        PrintCSBackupAPIErrorMessage(GetLastError());
        goto CleanUp0;
    }
    predictedLen = requiredLen;

    INFO("Probe Interface Data Buffer Length Succeed!\n");

    // Allocate a buffer after getting the required size.
    pDeviceInterfaceDetailData =
        (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, predictedLen);
    if (pDeviceInterfaceDetailData)
        pDeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
    else {
        PrintCSBackupAPIErrorMessage(GetLastError());
        goto CleanUp0;
    }

    INFO("Allocate Interface Data Buffer Length Succeed!\n");

    // Get the actual data for the Indirect Display interface
    if (!SetupDiGetDeviceInterfaceDetail(
        hardwareDevInfo,
        &deviceInterfaceData,
        pDeviceInterfaceDetailData,
        predictedLen,
        &requiredLen,
        NULL)) {
        PrintCSBackupAPIErrorMessage(GetLastError());
        goto CleanUp1;
    }


    std::_tcout << TEXT("Query Interface Data Succeed! Path: ")
        << pDeviceInterfaceDetailData->DevicePath
        << std::endl;

    hDeviceInterface = CreateFile(
        pDeviceInterfaceDetailData->DevicePath,
        dwDesiredAccess, // Important!!! Don't forget it!
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);
    if (hDeviceInterface == INVALID_HANDLE_VALUE) {
        PrintCSBackupAPIErrorMessage(GetLastError());
        goto CleanUp1;
    }

    INFO("Create Handle to Interface Data Succeed!\n");

    return hDeviceInterface;

CleanUp1:
    HeapFree(GetProcessHeap(), 0, pDeviceInterfaceDetailData);

CleanUp0:
    SetupDiDestroyDeviceInfoList(hardwareDevInfo);
    return INVALID_HANDLE_VALUE;
}


void startServer()
{	
	auto pipe_server = [&]() {
		HANDLE hPipe;
		std::string cmd;
		DWORD bytesWritten;
		BOOL bResult;

		hPipe = CreateNamedPipe(
			PIPE_NAME,
			PIPE_ACCESS_OUTBOUND,
			PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
			1,			// Only 1 instance
			1024,
			1024,
			0,			// Default timeout
			NULL		// Default security descriptor
		);
		if (hPipe == INVALID_HANDLE_VALUE) {
			INFO("[Server] Failed to Create a pipe.\n");
			GetStrLastError();
			return;
		}

		INFO("[Server] The pipe is created successfully.\n");

		INFO("[Server] Start reading stdin and send to Client\n");

		while (1) {
			std::cin >> cmd;
			if (cmd == ("q"))
				break;

			DWORD bytesToWrite = static_cast<DWORD>(cmd.size() + sizeof(TCHAR));

			bResult = WriteFile(
				hPipe,
				cmd.c_str(),
				bytesToWrite,
				&bytesWritten,
				NULL);

			if (!bResult || bytesWritten != bytesToWrite) {
				INFO("[Server] Failed to write to pipe:");
				GetStrLastError();
				break;
			}
		}

		INFO("[Server] Closing the pipe...\n");

		FlushFileBuffers(hPipe);
		DisconnectNamedPipe(hPipe);
		CloseHandle(hPipe);
	};

	std::thread t1(pipe_server);
	t1.detach();
	std::this_thread::sleep_for(std::chrono::seconds(1));
}

HANDLE ClientConnectToPipe(LPCTSTR lpszPipeName);

void PipeTest4_ReceiveImages()
{
	HANDLE hPipe = INVALID_HANDLE_VALUE;
	BOOL bResult;
	DWORD dwByteRead;

	std::unique_ptr<WCHAR> pWChar;
	hPipe = INVALID_HANDLE_VALUE;


	INFO("[Client] Start connecting the pipe.\n");

	hPipe = ClientConnectToPipe(PIPE_NAME);
	if (hPipe == INVALID_HANDLE_VALUE) {
		INFO("[Client] Failed to connect to the pipe: ");
		GetStrLastError();
		return;
	}

	INFO("[Client] Connected.\n");

	INFO("[Client] Wait server for unlocking.\n");

	do {
		dwByteRead = 0;

		bResult = ReadFile(
			hPipe,
			pWChar.get(),
			8, 
			&dwByteRead,
			NULL);

		if (!bResult &&
			GetLastError() != ERROR_MORE_DATA) { // Our buffer is not suffient for the whole data
			INFO("[Client] Read pipe error: ");
			GetStrLastError();
			break;
		}

		_tprintf(T("[Client] %u from server\n"), dwByteRead);
		

	} while (1);

	_tprintf(T("[Client] Closing...\n"));

	CloseHandle(hPipe);
}


HANDLE ClientConnectToPipe(LPCTSTR lpszPipeName) {
	HANDLE hPipe;
	DWORD dwMode;
	BOOL bResult;

	do {
		hPipe = CreateFile(
			lpszPipeName,
			GENERIC_READ | FILE_WRITE_ATTRIBUTES,	// It's required by SetNamedPipeHandleState
			0,										// No sharing
			NULL,									// Default security attributes
			OPEN_EXISTING,
			0,
			NULL);
		if (hPipe == INVALID_HANDLE_VALUE) {
			if (GetLastError() == ERROR_PIPE_BUSY) {
				WaitNamedPipe(lpszPipeName, NMPWAIT_WAIT_FOREVER);
				continue;
			}
			else
				return INVALID_HANDLE_VALUE;
		}
		break;
	} while (1);

	dwMode = PIPE_READMODE_MESSAGE;
	bResult = SetNamedPipeHandleState(
		hPipe,
		&dwMode,
		NULL,
		NULL);
	if (!bResult) {
		CloseHandle(hPipe);
		return INVALID_HANDLE_VALUE;
	}

	return hPipe;
}
